package firbi.base;

import java.util.List;

import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.test.ActivityUnitTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

import firbi.base.com.BusStop;
import firbi.base.util.Constants;

/**
 * These unit tests test Search. It goes through the methods that will be called
 * when the use enters input, clicks the GPS button, and interacts with the Search such
 * as switching views or clicking on a list. The tests also cover interactions between
 * other activities to make sure Search behaves correctly. 
 * Can be run with the following command:
 * adb shell am instrument \
 * 	-e class firbi.base.SearchUnitTests \
 *	-w firbi.base.tests/android.test.InstrumentationTestRunner
 */
public class SearchUnitTest extends ActivityUnitTestCase<Search> {
	private Intent mIntent;
	private GeoPoint mLoc;
	private BusStop[] mStopsBad;
	private BusStop[] mStopsGood;
	private int mNumStops;

    public SearchUnitTest() {
        super(Search.class);
      }
    protected void setUp() throws Exception {
    	super.setUp();
    	mIntent = new Intent(Intent.ACTION_MAIN);
    	mNumStops = 5;
		mIntent = new Intent(Intent.ACTION_MAIN);
		mStopsBad = new BusStop[0];
		mStopsGood = new BusStop[5];
		for (int i = 0; i < mNumStops; i++) {
			mStopsGood[i] = BusStop.updateOrCreate("808", "12345", new GeoPoint(123456, 654321), "123 fake st", 1);
		}
		mLoc = new GeoPoint (47619800, -122347000);
    }
    protected void tearDown() throws Exception {
    	super.tearDown();
    }
    
    /**
     * Tests listBus by looking at the number of current bus stops
     * and the number of items displayed on the list. 
     */
    @SmallTest
    public void testListBus() {
    	Search myActivity = startActivity(mIntent, null, null);
    	myActivity.turnOffProgressDialog();
    	ListView myList = (ListView)getActivity().findViewById(R.id.list);
    	TextView myListEmptyText = (TextView)getActivity().findViewById(R.id.list_empty);
    	/* Number of values in adapter includes headers */
    	int rightNum = myList.getHeaderViewsCount();
    	
    	assertNull("No bus stops at start", myActivity.getCurrentBusStops());
    	/* Check null input */
    	myActivity.listBus(null, null);
  
    	/* Check if list contains items */
    	myActivity.switchViews();
    	assertFalse("In List", myActivity.isMapView());
    	myActivity.listBus(mStopsGood, mLoc);
    	rightNum += mNumStops;
    	assertNotNull("List contains items", myList.getAdapter());
    	assertEquals("Size of list", rightNum, myList.getAdapter().getCount());
    	/* Check if bad stops clears the list */
    	myActivity.listBus(mStopsBad, mLoc);
    	rightNum -= mNumStops;
    	assertEquals("List no stops", rightNum, myList.getAdapter().getCount());
    	assertEquals("List empty display", "No Bus Stops Found", myListEmptyText.getText().toString());
    }
	
    /**
     * This tests displayBus by looking at the size of the map's overlays
     * when the method is called with different inputs. 
     */
    @SmallTest
    public void testDisplayBus() {
    	Search myActivity = startActivity(mIntent, null, null);
    	myActivity.turnOffProgressDialog();
    	MapView myMap = (MapView)getActivity().findViewById(R.id.map);
    	List<Overlay> mapOverlays = myMap.getOverlays();
    	int rightSize = 1;
    	assertEquals("MyLocationOverlay", rightSize, mapOverlays.size());
 
    	/* Add an overlay */
    	assertTrue("In map", myActivity.isMapView());
    	myActivity.displayBus(mStopsGood);
    	rightSize++;
    	assertEquals("Map overlays contains CurrentStops", rightSize, mapOverlays.size());
    	
    	/* A failed display should not change the displays on the map */
    	myActivity.displayBus(mStopsBad);
    	assertEquals("Map overlays unchanged", rightSize, mapOverlays.size());
    	myActivity.displayBus(mStopsBad);
    	assertEquals("Map overlays unchanged--repeated", rightSize, mapOverlays.size());
    	
    	myActivity.removeAllOverlays();
    	rightSize = 1;
    	assertEquals("No map overlays", rightSize, mapOverlays.size());
    	
    	/* Null checks */
    	myActivity.displayBus(null);
    	assertEquals("No map overlays--null", rightSize, mapOverlays.size());
    	myActivity.displayBus(mStopsGood);
    	rightSize++;
    	myActivity.displayBus(null);
    	assertEquals("Null does not change overlays", rightSize, mapOverlays.size());
    }
    
    
    /**
     * Tests refreshBusStops by checking if map or list
     * is correctly updated depending on the current view.
     */
    @SmallTest
    public void testRefreshBusStops() {
    	Search myActivity = startActivity(mIntent, null, null);
    	myActivity.turnOffProgressDialog();
    	GeoPoint point = new GeoPoint(12345, 654321); 
    	MapView myMap = (MapView)myActivity.findViewById(R.id.map);
    	ListView myList = (ListView)myActivity.findViewById(R.id.list);
    	
    	List<Overlay> myMapOverlays = myMap.getOverlays();
    	int mapCount = myMapOverlays.size();
    	int listCount = myList.getHeaderViewsCount();
    	
    	/* Map displays -- list is invisible */
    	assertTrue("In map", myActivity.isMapView());
    	myActivity.refreshBusStops(point, mStopsGood);
    	mapCount++;
    	assertEquals("Added to map", mapCount, myMapOverlays.size());
    	assertEquals("Not added to list", 0, myList.getCount());
    	
    	/* List displays -- adapter always counts header */
    	myActivity.switchViews();
    	mapCount--;
    	assertFalse("In List", myActivity.isMapView());
    	myActivity.refreshBusStops(point, mStopsGood);
    	listCount += mNumStops;
    	assertEquals("Not added to map", mapCount, myMapOverlays.size());
    	assertEquals("Added to list", listCount, myList.getCount());	
    }
    
    /**
     * This tests removeAllOverlays by checking the size of the map's overlays
     * when the function is called at various points. 
     */
    @SmallTest
    public void testRemoveAllOverlays() {
       	Search myActivity = startActivity(mIntent, null, null);
       	myActivity.turnOffProgressDialog();
    	MapView myMap = (MapView)getActivity().findViewById(R.id.map);
    	List<Overlay> mapOverlays = myMap.getOverlays();
    	int rightSize = 1;
    	assertEquals("MyLocation overlay", rightSize, mapOverlays.size());
    	
    	/* Removes on an empty map */
    	myActivity.removeAllOverlays();
    	myActivity.removeAllOverlays();
    	myActivity.displayBus(null);
    	myActivity.removeAllOverlays();
    	assertEquals("No overlays", rightSize, mapOverlays.size());
    	
    	/* Add some overlays then remove them */
    	myActivity.displayBus(mStopsGood);
    	rightSize++;
    	assertEquals(rightSize, mapOverlays.size());
    	myActivity.removeAllOverlays();
    	rightSize = 1;
    	assertEquals("No overlays after add/remove", rightSize, mapOverlays.size());
    }
    /** 
     * The purpose of this test is to test the content of the intent send to route view.
     * @throws Exception
     */
    @SmallTest
    public void testToRouteView() throws Exception{
    	Search myActivity = startActivity(mIntent, null, null);
    	GeoPoint realOne = new GeoPoint(1234, -54321);
    	String realNum = "123";
    	
    	myActivity.toRouteView(realNum, realOne);
    	Thread.sleep(2000);
    	Intent i = getStartedActivityIntent();
    	assertNotNull("Called route", i);
    	Bundle b = i.getExtras();
    	GeoPoint sentOne = new GeoPoint (b.getInt(Constants.sROUTE_LAT_KEY), b.getInt(Constants.sROUTE_LON_KEY));
    	assertEquals("Send right geopoint", realOne, sentOne);
    	assertEquals("Sent right route", realNum, b.getString(Constants.sROUTE_KEY));
    }
    
    /**
     * The purpose of this test to to test the content of the intent send to stopview.
     * @throws Exception
     */
    @SmallTest
    public void testToStopView() throws Exception {
    	Search myActivity = startActivity(mIntent, null, null);
    	String realNum = "98765";
    	
    	myActivity.toStopView(realNum);
    	Thread.sleep(2000);
    	Intent i = getStartedActivityIntent();
    	assertNotNull("Called stop", i);
    	Bundle b = i.getExtras();
    	assertEquals("Sent right route", realNum, b.getString(Constants.sSTOP_KEY));
    }
    /**
     * The purpose of this test is to test the input classifier to make sure
     * it correctly labels the user input.
     */
    @SmallTest
    public void testCheckInputType() {
    	Search myActivity = startActivity(mIntent, null, null);
    	
    	String [] goodRoutes = {"1", "999"};
    	String [] badRoutes = {"71E", "abc"};
    	String [] goodStop = {"12345"};
    	String [] address = {"1234g", "4th and 9th"};
    	
    	int type;
    	for (int i = 0; i < goodRoutes.length; i++) {
    	   	type = myActivity.checkInputType(goodRoutes[i]);
    	   	assertEquals("Good Routes" + i, Search.INPUT_ROUTE, type);
    	}
    	for (int i = 0; i < badRoutes.length; i++) {
    	   	type = myActivity.checkInputType(badRoutes[i]);
    	   	assertEquals("Bad Routes" + i, Search.INPUT_BAD_ROUTE, type);
    	}
    	for (int i = 0; i < goodStop.length; i++) {
    	   	type = myActivity.checkInputType(goodStop[i]);
    	   	assertEquals("Good Stop" + i, Search.INPUT_BUS_STOP, type);
    	}
    	for (int i = 0; i < address.length; i++) {
    	   	type = myActivity.checkInputType(address[i]);
    	   	assertEquals("Address" + i, Search.INPUT_ADDRESS, type);
    	}	
    }
    
    /**
     * The purpose of this test is to check if Search will behave properly
     * if passed in an intent to signal which view (Map/List) must be displayed.
     */
    @SmallTest
    public void testReceiveIntents() {
    	/* Search should start in list */
    	Intent fromOther = new Intent();
    	fromOther.putExtra("isMapView", false);
    	Search myActivity = startActivity(fromOther, null, null);
    	assertFalse("In list", myActivity.isMapView());
    	myActivity.finish();
    }
    
    
    /** Tests if the GPS Button will correctly update the location when it is pressed
     *  also checks the bus stops returned near that location.
     *
     */
    @MediumTest
	public void testGPSButton() throws Exception {
		final Search myActivity = startActivity(mIntent, null, null);
		myActivity.turnOffProgressDialog();
		final ImageButton myGPSButton = (ImageButton)myActivity.findViewById(R.id.update_gps_button);
		
		LocationManager locMan = (LocationManager)myActivity.getSystemService(Context.LOCATION_SERVICE);
		locMan.setTestProviderStatus(LocationManager.GPS_PROVIDER,
				LocationProvider.AVAILABLE, null, System.currentTimeMillis());
		locMan.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);

		final Location loc1 = new Location(LocationManager.GPS_PROVIDER);
		final Location loc2 = new Location(LocationManager.GPS_PROVIDER);
		
		loc1.setLatitude(5.11d);
		loc1.setLongitude(-10.456d);
		loc2.setLatitude(47.6198d);
		loc2.setLongitude(-122.347d);
		loc1.setTime(System.currentTimeMillis());
		loc2.setTime(System.currentTimeMillis());
		
		final GeoPoint point = LocToGeo(loc1);
		final GeoPoint point2 = LocToGeo(loc2);
		
		/* Get a GPS Signal for a bad location with no bus stops and wait for it to update */
		locMan.setTestProviderLocation(LocationManager.GPS_PROVIDER, loc1);
		locMan.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);

		Thread.sleep(2000);
		myGPSButton.performClick();
		Thread.sleep(2000);
		assertEquals("My Location 1", point, myActivity.getMyLocation());
		assertNull("No stops!", myActivity.getCurrentBusStops());

		/* Get a GPS Signal for a good location with bus stops and wait for it to update */
		locMan.setTestProviderLocation(LocationManager.GPS_PROVIDER, loc2);
		locMan.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
		Thread.sleep(2000);
		myGPSButton.performClick();
		Thread.sleep(2000);
		assertEquals("My Location 2", point2, myActivity.getMyLocation());
		assertNotNull("Got stops!",  myActivity.getCurrentBusStops());
		
		/* Nothing should change */
		myGPSButton.performClick();
		Thread.sleep(2000);
		assertEquals("My Location 2", point2, myActivity.getMyLocation());
		assertNotNull("Got stops!",  myActivity.getCurrentBusStops());
		
		
		/* Back to the bad location  */
		locMan.setTestProviderLocation(LocationManager.GPS_PROVIDER, loc1);
		locMan.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
		Thread.sleep(2000);
		myGPSButton.performClick();
		Thread.sleep(2000);
		assertEquals("My Location 1", point, myActivity.getMyLocation());
		assertNull("Got No stops!",  myActivity.getCurrentBusStops());
    }
	
	/** 
	 * Tests if switchView correctly toggles between map and list view.
	 * Checks by looking at the views' visibility and if that view currently
	 * contains Bus Stops found.
	 */
    @SmallTest
	public void testSwitchViews() {
		final Search myActivity = startActivity(mIntent, null, null);
		int size = 10;
		BusStop [] stops = new BusStop[size];
		for (int i = 0; i < stops.length; i++) {
			stops[i]=BusStop.updateOrCreate(i+"", i+50+"", new GeoPoint(12345, 54321), "123 maple road", 1);
		}
		
		myActivity.turnOffProgressDialog();
    	setupGoodLocation(myActivity);
		RelativeLayout myMapLayout = (RelativeLayout)getActivity().findViewById(R.id.map_layout);
		LinearLayout myListLayout = (LinearLayout)getActivity().findViewById(R.id.list_layout);
		MapView myMap = (MapView)getActivity().findViewById(R.id.map);
		ListView myList = (ListView)getActivity().findViewById(R.id.list);
		int startCount = myList.getHeaderViewsCount();
		
		/* Start in map -- add bus stops to map */
		assertEquals("Map visible 1", View.VISIBLE, myMapLayout.getVisibility());
		assertEquals("List invisible 1", View.INVISIBLE, myListLayout.getVisibility());
		myActivity.refreshBusStops(new GeoPoint(1234556, 53242), stops);
		assertEquals("List should be empty", 0, myList.getCount());
		
		/* In list */
		myActivity.switchViews();
		assertEquals("Map invisible 2", View.INVISIBLE, myMapLayout.getVisibility());
		assertEquals("List visible 2", View.VISIBLE, myListLayout.getVisibility());
		assertEquals("List contains stops", size+startCount, myList.getAdapter().getCount());
		
		myActivity.switchViews();
		assertEquals("Map visible 3", View.VISIBLE, myMapLayout.getVisibility());
		assertEquals("List invisible 3", View.INVISIBLE, myListLayout.getVisibility());
    }
	
	/**
	 *  Test if clicking on a list item will send an intent to continue the query.
	 *  Also test the content that bundle to make sure the correct key is sent.
	 */
	@SmallTest
	public void testListClick() {
		final Search myActivity = startActivity(mIntent, null, null);
		myActivity.turnOffProgressDialog();
		ListView list = (ListView) myActivity.findViewById(R.id.list);
		setupGoodLocation(myActivity);
		myActivity.switchViews();
		list.performItemClick(null, 1, 0);
		Intent i = getStartedActivityIntent() ;
		assertNotNull("Opens an other activity", i);
		Bundle b = i.getExtras();
		assertTrue("Sending correct field", b.containsKey("bus_stop_id"));
		assertNotNull("Field is not null", b.get("bus_stop_id"));
	}
 

    /* Converts a location to a geopoint */
    private GeoPoint LocToGeo (Location loc) {
    	return new GeoPoint( (int) (loc.getLatitude()*1E6), (int)(loc.getLongitude()*1E6));
    }
    /* Sets up the GPS to a good location -- same one used in testGPSButton */
    private void setupGoodLocation(final Search myActivity) {
		final ImageButton GPSButton = (ImageButton)myActivity.findViewById(R.id.update_gps_button);
	
		LocationManager locMan = (LocationManager)myActivity.getSystemService(Context.LOCATION_SERVICE);
		locMan.setTestProviderStatus(LocationManager.GPS_PROVIDER,
				LocationProvider.AVAILABLE, null, System.currentTimeMillis());
		Location loc2 = new Location(LocationManager.GPS_PROVIDER);
		loc2.setLatitude(47.6198d);
		loc2.setLongitude(-122.347d);
		loc2.setTime(System.currentTimeMillis());
		final GeoPoint point2 = LocToGeo(loc2);

		Thread click = new Thread() {
			public void run() {
				GPSButton.performClick();
			}
		};
		Thread check2 = new Thread() {
			public void run() {
				assertEquals("My Location 2 --setup", point2, myActivity.getMyLocation());
				assertNotNull("Got stops! --setup",  myActivity.getCurrentBusStops());
			}
		};
		locMan.setTestProviderLocation(LocationManager.GPS_PROVIDER, loc2);
		locMan.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
		try {
			Thread.sleep(3000);
			click.run();
			Thread.sleep(3000);
			check2.run();
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
    }
    
}
    

